package org.evolAI.organism.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import org.evolAI.organism.util.RandomNum;

@Entity
@Table(name="MemoryGroup")
@XmlRootElement
public class MemoryGroup implements Map<String, Memory> {

	private long id;
	private Map<String, Memory> memories = new HashMap<String, Memory>();
	
	public MemoryGroup() {
		
	}
	
	protected MemoryGroup(MemoryGroup group) {
		for (Entry<String, Memory> entry: group.entrySet()) {
			this.put(entry.getKey(), entry.getValue().copy());
		}
	}
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "memory_group_id")
	public long getId() {
		return this.id;
	}
	
	public void setId(long id) {
		this.id = id;
	}

	@OneToMany(cascade=CascadeType.ALL)
	@MapKey
	public Map<String, Memory> getMemories() {
		return memories;
	}
	
	public void setMemories(Map<String, Memory> memories) {
		this.memories = memories;
	}
	
    public String recallRandom() {
        int weight = 0;
        int rndMemory = RandomNum.getUniform(totalWeight());

        for (String memory: this.keySet()) {
            weight += this.get(memory).getWeight();
            if (rndMemory < weight)
                return memory;
        }
        return "";
    }

    public String recallStrongest() {
        int weight = 0;
        String memory = "";

        for (String aMemory: this.keySet()) {
            if (this.get(aMemory).getWeight() > weight) {
                memory = aMemory;
                weight = this.get(aMemory).getWeight();
            }
        }
        return memory;
    }

    public String recallEarliest() {
        Date earliest = new Date(Long.MAX_VALUE);
        String memory = "";

        for (String aMemory: this.keySet()) {
            if (this.get(aMemory).getCreated().before(earliest)) {
                memory = aMemory;
                earliest = this.get(aMemory).getCreated();
            }
        }
        return memory;
    }

    public String recallLatest() {
        Date latest = new Date(Long.MIN_VALUE);
        String memory = "";

        for (String aMemory: this.keySet()) {
            if (this.get(aMemory).getCreated().after(latest)) {
                memory = aMemory;
                latest = this.get(aMemory).getCreated();
            }
        }
        return memory;
    }

    public int totalWeight() {
        int weight = 0;
        for (Memory memory: this.values()) {
            weight += memory.getWeight();
        }
        return weight;
    }

    public void remember(String memory) {
        if (this.containsKey(memory)) {
            Memory aMem = this.get(memory);
            aMem.setWeight(aMem.getWeight() + 1);
            aMem.setUpdated(new Date());
        }
        else
            addMemory(memory);
    }

    public void addMemory(String memory) {
        this.put(memory, new Memory());
    }

    public MemoryGroup copy() {
    	return new MemoryGroup(this);
    }

    public void degrade() {
        for (Memory aMemory: this.values()) {
            aMemory.setWeight(aMemory.getWeight() - 1);
        }
    }

	@Transient
    public List<String> getList() {
        List<String> newList = new ArrayList<String>();

        for (String newString: this.keySet()) {
            newList.add(newString);
        }

        return newList;
    }

	@Transient
    public List<String> getListByWeight() {
        SortedMap<Integer, List<String>> sorted = new TreeMap<Integer, List<String>>();
        List<String> newList = new ArrayList<String>();

        for (String mem: this.keySet()) {
            int key = this.get(mem).getWeight();
            if (!sorted.containsKey(key)) {
                sorted.put(key, new ArrayList<String>());
            }
            sorted.get(key).add(mem);
        }

        for (List<String> mem: sorted.values()) {
            for (String aMem: mem) {
                newList.add(aMem);
            }
        }

        Collections.reverse(newList);
        return newList;
    }

	@Transient
    public List<String> getListByDateEarliest() {
        SortedMap<Date, List<String>> sorted = new TreeMap<Date, List<String>>();
        List<String> newList = new ArrayList<String>();

        for (String mem: this.keySet()) {
            Date key = this.get(mem).getCreated();
            if (!sorted.containsKey(key)) {
                sorted.put(key, new ArrayList<String>());
            }
            sorted.get(key).add(mem);
        }

        for (List<String> mem: sorted.values()) {
            for (String aMem: mem) {
                newList.add(aMem);
            }
        }

        return newList;
    }

	@Transient
    public List<String> getListByDateLatest()
    {
    	List<String> newList = getListByDateEarliest();
    	Collections.reverse(newList);
        return newList;
    }

	@Override
	public int size() {
		return memories.size();
	}

	@Override
	@Transient
	public boolean isEmpty() {
		return memories.isEmpty();
	}

	@Override
	public boolean containsKey(Object key) {
		return memories.containsKey(key);
	}

	@Override
	public boolean containsValue(Object value) {
		return memories.containsValue(value);
	}

	@Override
	public Memory get(Object key) {
		return memories.get(key);
	}

	@Override
	public Memory put(String key, Memory value) {
		return memories.put(key, value);
	}

	@Override
	public Memory remove(Object key) {
		return memories.remove(key);
	}

	@Override
	public void putAll(Map<? extends String, ? extends Memory> m) {
		memories.putAll(m);
	}

	@Override
	public void clear() {
		memories.clear();
	}

	@Override
	public Set<String> keySet() {
		return memories.keySet();
	}

	@Override
	public Collection<Memory> values() {
		return memories.values();
	}

	@Override
	public Set<java.util.Map.Entry<String, Memory>> entrySet() {
		return memories.entrySet();
	}	
}
